﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Proggy.Data.Abstract;
using Proggy.Data.Entities;
using System.Web;
using System.Web.Mvc;
using Proggy.Web.Cms.Context;
using Proggy.Web.Cms.Cache;
using Proggy.Web.Cms.Repositories.ContentModel;
using Proggy.Web.Cms.ContentModel;
using System.Web.Mvc.Html;

namespace Proggy.Web.Cms.ViewModels
{
    /// <summary>
    /// The site map view model
    /// </summary>
    public class SiteMapViewModel
    {
        /// <summary>
        /// The site map view model
        /// </summary>
        /// <param name="open"></param>
        /// <param name="selected"></param>
        public SiteMapViewModel(string areaId, string open, string selected, bool includeContentMenu, bool useLiveLinksAndNodes, INodeRepository repo, IContentModelTypeRepository cmRepo)
        {
            // set home node
            this.RootNode = repo.GetRootNode();// this.AllNodes.SingleOrDefault(x => x.ParentNode == null);// repo.GetRootNode();

            // set rendering
            this.CurrentlyRenderingNode = this.RootNode;

            // check
            _liveTestOk = (this.UseLiveLinksAndNode && this.CurrentlyRenderingNode.IsPublished) || !this.UseLiveLinksAndNode;
            if (!_liveTestOk)
            {
                return;
            }

            // check
            if (string.IsNullOrWhiteSpace(areaId))
            {
                throw new Exception("AreaId on sitemap action cannot be empty");
            }

            // set repo
            this.ContentModelTypeRepository = cmRepo;
            //this.ContentModelRepository = DependencyResolver.Current.GetService<IContentModelTypeRepository>();

            // set area id
            this.AreaId = areaId;

            // set content menu
            this.IncludeContentMenu = includeContentMenu;

            // set live
            this.UseLiveLinksAndNode = useLiveLinksAndNodes;

            // set all nodes
            //this.AllNodes = repo.PublishableNodes.ToList();

            // set icons
            this.Icons =
                this
                .ContentModelTypeRepository
                .AvailableContentModels
                .ToDictionary(x => x.TypeAlias, x => x.ContentModelAttribute.PathToIcon);


            // check
            if (string.IsNullOrEmpty(open))
            {
                this.Opens = new List<PublishableNode>(1) { 
                    this.RootNode
                };
                this.OpenIds = new List<int>(1) { this.RootNode.PublishableNodeId };
            }
            else
            {

                // set opens
                var openArr = open.Contains(',') ? open.Split(',') : new string[] { open };
                this.OpenIds = openArr.Select(x => Convert.ToInt32(x)).ToList();
                this.Opens = this.OpenIds.Select(x =>
                    repo.GetPublishableNodes(false).Single(y => y.PublishableNodeId == x)
                ).ToList();

                //repo.GetPublishableNodeFromNodeId(Convert.ToInt32(x))).ToList();
            }

            // check
            if (string.IsNullOrEmpty(selected))
            {
                this.Selecteds = new List<PublishableNode>(0);
                this.SelectedIds = new List<int>(0);
            }
            else
            {

                // set selecteds
                var selectedArr = selected.Contains(',') ? selected.Split(',') : new string[] { selected };
                this.SelectedIds = selectedArr.Select(x => Convert.ToInt32(x)).ToList();
                this.Selecteds = this.SelectedIds.Select(x => repo.GetPublishableNodes(false).Single(y => y.PublishableNodeId == x)).ToList();

            }

            // check selecteds
            foreach (var slct in Selecteds.Where(x => x.ParentNode != null))
            {
                // 
                if (!Opens.Contains(slct.ParentNode))
                {
                    Opens.Add(slct.ParentNode);
                    OpenIds.Add(slct.ParentNode.PublishableNodeId);
                }
            }

            // get current open enumarated
            var currentOpenEnumerated = Opens.Where(x => x.ParentNode != null).ToList();

            // check opens
            foreach (var opn in currentOpenEnumerated)
            {
                // 
                checkParentForOpen(opn);
            }
        }

        /// <summary>
        /// Content model data repo
        /// </summary>
        public IContentModelTypeRepository ContentModelTypeRepository { get; set; }

        /// <summary>
        /// Checks that open's parent is open
        /// </summary>
        /// <param name="opn"></param>
        private void checkParentForOpen(PublishableNode opn)
        {
            // check
            if ((opn.ParentNode != null) && (!this.OpenIds.Contains(opn.ParentNode.PublishableNodeId)))
            {

                // add
                this.Opens.Add(opn.ParentNode);
                this.OpenIds.Add(opn.ParentNode.PublishableNodeId);

                // check again
                checkParentForOpen(opn.ParentNode);
            }
        }

        /// <summary>
        /// The root node
        /// </summary>
        public PublishableNode RootNode { get; set; }

        /// <summary>
        /// The open nodes
        /// </summary>
        public List<PublishableNode> Opens { get; set; }

        /// <summary>
        /// the selected nodes
        /// </summary>
        public List<PublishableNode> Selecteds { get; set; }

        public List<int> SelectedIds { get; set; }
        public List<int> OpenIds { get; set; }

        /// <summary>
        /// The node being rendered
        /// </summary>
        public PublishableNode CurrentlyRenderingNode { get; set; }

        /// <summary>
        /// Html area id (for div)
        /// </summary>
        public string AreaId { get; set; }

        /// <summary>
        /// If true, will show content menu too
        /// </summary>
        public bool IncludeContentMenu { get; set; }

        /// <summary>
        /// If true, will only display published nodes and links
        /// </summary>
        public bool UseLiveLinksAndNode { get; set; }

        /// <summary>
        /// Query string for opening node
        /// </summary>
        /// <param name="rend"></param>
        /// <returns></returns>
        public string GetOpenQueryStringForNode()
        {
            // start
            var output = new StringBuilder();

            // get opens as list of ints
            var opensListForQueryString = this.Opens.Select(x => x.PublishableNodeId).ToList();

            // check
            if (_hasChildren)
            {
                // add this
                opensListForQueryString.Add(this.CurrentlyRenderingNode.PublishableNodeId);
            }

            // add
            output.Append("?open=");
            output.Append(string.Join(",", opensListForQueryString));

            // add
            output.Append("&selected=");
            output.Append(string.Join(",", this.SelectedIds));

            // add include
            if (HttpContext.Current != null && HttpContext.Current.Request != null && !string.IsNullOrEmpty(HttpContext.Current.Request["includeContentMenu"]) && HttpContext.Current.Request["includeContentMenu"].ToLower() == "true")
            {
                output.Append("&includeContentMenu=true");
            }

            if (HttpContext.Current != null && HttpContext.Current.Request != null && !string.IsNullOrEmpty(HttpContext.Current.Request["pickerId"]))
            {
                output.Append("&pickerId=");
                output.Append(HttpContext.Current.Request["pickerId"]);
            }



            // return
            return output.ToString();
        }

        /// <summary>
        /// Query string for closing node
        /// </summary>
        /// <param name="rend"></param>
        /// <returns></returns>
        public string GetCloseQueryStringForNode()
        {
            // start
            var output = new StringBuilder();




            // get list
            var opensList = this.Opens.Select(x => x.PublishableNodeId).ToList();// this.OpenIds;

            // remove this and children from open collection
            removeChildrenFromCollection(this.CurrentlyRenderingNode, ref opensList);





            // add
            output.Append("?open=");
            output.Append(string.Join(",", opensList));

            //// add
            //output.Append("&selected=");
            //output.Append(string.Join(",", selectedList));

            // add include
            if (HttpContext.Current != null && HttpContext.Current.Request != null && !string.IsNullOrEmpty(HttpContext.Current.Request["includeContentMenu"]) && HttpContext.Current.Request["includeContentMenu"].ToLower() == "true")
            {
                output.Append("&includeContentMenu=true");
            }

            if (HttpContext.Current != null && HttpContext.Current.Request != null && !string.IsNullOrEmpty(HttpContext.Current.Request["pickerId"]))
            {
                output.Append("&pickerId=");
                output.Append(HttpContext.Current.Request["pickerId"]);
            }

            // return
            return output.ToString();
        }

        /// <summary>
        /// Removes child from opens
        /// </summary>
        /// <param name="rend"></param>
        /// <param name="output"></param>
        private void removeChildrenFromCollection(PublishableNode rend, ref List<int> opens)
        {
            if (opens.Count > 0)
            {
                // remove
                if (opens.Contains(rend.PublishableNodeId))
                {
                    opens.Remove(rend.PublishableNodeId);
                }

                if (opens.Count > 0)
                {

                    // 
                    var children = rend.ChildNodes;
                    var hasChildren = children.Count > 0;

                    // check children
                    if (hasChildren && opens.Count > 0)
                    {
                        // render
                        foreach (var child in children)
                        {
                            if (opens.Count > 0)
                            {
                                //
                                removeChildrenFromCollection(child, ref opens);
                            }
                            else
                            {
                                break;
                            }
                        }
                    }
                    else
                    {
                        return;
                    }
                }
                else
                {
                    return;
                }
            }
            else
            {
                return;
            }
        }

        /// <summary>
        /// Get's live link for node
        /// </summary>
        /// <param name="rend"></param>
        /// <returns></returns>
        public MvcHtmlString GetLiveLink()
        {
            if (this.CurrentlyRenderingNode.IsPublished)
            {
                // check
                if (_cachedUrl != null)
                {
                    //
                    return MvcHtmlString.Create("<a class=\"proggy-sm-item-link\" href=\"" + _liveLink + "\">" + this.CurrentlyRenderingNode.Name + "</a>");
                }


            }

            return MvcHtmlString.Create(this.CurrentlyRenderingNode.Name);
        }

        /// <summary>
        /// Icons for nodes
        /// </summary>
        public Dictionary<string, string> Icons { get; set; }

        /// <summary>
        /// Returns icon src attribute value for currently rendering node (eg "~/Content/IMage...")
        /// </summary>
        /// <returns></returns>
        public string GetIconSrcForCurrentNode()
        {
            try
            {
                var match = this.Icons[this.CurrentlyRenderingNode.TypeAlias];
                if (!string.IsNullOrEmpty(match))
                {
                    return match;
                }
            }
            catch
            {

            }
            return "~/Areas/ProggyCore/Images/ContentModelIcons/Folder.png";
        }

        /// <summary>
        /// Render site map from code
        /// </summary>
        /// <param name="w"></param>
        /// <param name="html"></param>
        public void RenderSiteMap(System.IO.TextWriter w, HtmlHelper html)
        {
            // set
            _cachedUrl = UrlCache.CachedNodeUrls.SingleOrDefault(x => x.NodeId == this.CurrentlyRenderingNode.PublishableNodeId);
            _liveLink = _cachedUrl != null ? _cachedUrl.NodeUrl : null;
            _draft = this.CurrentlyRenderingNode.DraftNode;
            _useDraft = _draft != null;
            _name = _useDraft ? _draft.Name : this.CurrentlyRenderingNode.Name;
            _open = this.OpenIds.Contains(this.CurrentlyRenderingNode.PublishableNodeId);
            _selected = this.SelectedIds.Contains(this.CurrentlyRenderingNode.PublishableNodeId);
            _childNodes = this.CurrentlyRenderingNode.ChildNodes.OrderBy(x => x.OrderInParent);
            _parent = this.CurrentlyRenderingNode.ParentNode;
            _siblings = _parent == null ? null : _parent.ChildNodes.Where(x => x.PublishableNodeId != this.CurrentlyRenderingNode.PublishableNodeId);

            // check
            if (this.UseLiveLinksAndNode)
            {
                _parent = _parent != null && _parent.IsPublished ? _parent : null;
                _childNodes = _childNodes.Where(x => x.IsPublished);
                _siblings = _siblings == null ? null : _siblings.Where(x => x.IsPublished);
            }

            // set
            _hasParent = _parent != null;
            _childCount = _childNodes.Count();
            _hasChildren = _childCount > 0;
            _hasSiblings = _siblings != null && _siblings.Count() > 0;

            // get rendering strings
            _linkQuery = MvcHtmlString.Create(_open ? this.GetCloseQueryStringForNode() : this.GetOpenQueryStringForNode());
            _linkClass = _open ? "proggy-sm-link-open" : "proggy-sm-link-closed";
            _linkText = _open ? "Close" : "Open";

            // get link
            _link = this.UseLiveLinksAndNode ?
                this.GetLiveLink()
                :
                LinkExtensions.ActionLink(html, _name, "Preview", new
                {
                    controller = "Content",
                    area = "Proggy",
                    id = this.CurrentlyRenderingNode.PublishableNodeId
                }, new
                {
                    @class = "proggy-sm-preview-link proggy-sm-preview-link-selected" + _selected,
                    data_nodeid = this.CurrentlyRenderingNode.PublishableNodeId
                });

            // em
            if (_useDraft && this.IncludeContentMenu)
            {

                _link = MvcHtmlString.Create("<em>" + HttpUtility.HtmlEncode(_link) + "</em>");

            }

            // write start of li
            w.WriteLine(MvcHtmlString.Create("<li class=\"proggy-sm-selected" + _selected + "\" style=\"background-image: url('" + UrlHelper.GenerateContentUrl(this.GetIconSrcForCurrentNode(), html.ViewContext.HttpContext) + "');\">"));

            // check if root and write no link 
            if (this.CurrentlyRenderingNode.IsRoot)
            {
                w.WriteLine(MvcHtmlString.Create("<span class=\"proggy-sm-link proggy-sm-link-open\"><span class=\"hidden\">" + _selected + "</span></span>"));
            }
            else
            {
                // check if has children and write link if so
                if (_hasChildren)
                {
                    w.WriteLine(MvcHtmlString.Create("<a class=\"proggy-sm-link " + _linkClass + "\" href=\"" + html.ViewContext.HttpContext.Request.Path + _linkQuery + "\"><span class=\"hidden\">" + _linkText + "</span></a>"));
                }
                else
                {
                    // no link
                    w.WriteLine(MvcHtmlString.Create("<span class=\"proggy-sm-link\">&nbsp;</span>"));
                }
            }

            // write dummy link
            w.WriteLine(MvcHtmlString.Create(_link + " &nbsp;&nbsp;&nbsp;&nbsp;<a href=\"#\">&nbsp;</a>"));

            // write content menu
            //if (!this.IncludeContentMenu)
            if (this.IncludeContentMenu)
            {
                w.WriteLine(MvcHtmlString.Create("<span class=\"tree-item-menu\" data-nodeid=\"" + this.CurrentlyRenderingNode.PublishableNodeId + "\"><span class=\"strong\">"));
                w.WriteLine(MvcHtmlString.Create("Options: </span>"));
                w.WriteLine(LinkExtensions.ActionLink(html, "Create new here", "Create", new { parentNodeId = this.CurrentlyRenderingNode.PublishableNodeId }));
                w.WriteLine("|");
                w.WriteLine(LinkExtensions.ActionLink(html, "Edit", "Edit", new { id = this.CurrentlyRenderingNode.PublishableNodeId }));
                if (_useDraft)
                {
                    w.WriteLine(MvcHtmlString.Create(" | " + LinkExtensions.ActionLink(html, "Publish", "Publish", new { id = this.CurrentlyRenderingNode.PublishableNodeId })));
                    w.WriteLine(MvcHtmlString.Create(" | " + LinkExtensions.ActionLink(html, "Preview", "Preview", new { id = this.CurrentlyRenderingNode.PublishableNodeId }, new { target = "_blank", @class = "proggy-content-preview" })));
                }
                if (this.CurrentlyRenderingNode.IsPublished)
                {
                    //w.WriteLine(MvcHtmlString.Create(" | " + LinkExtensions.ActionLink(html, "View published", "HandleUrl", new
                    //{
                    //    controller = this.CurrentlyRenderingNode.TypeAlias,
                    //    //url = model.UrlPortion,
                    //    nodeId = this.CurrentlyRenderingNode.PublishableNodeId,
                    //    json = false,
                    //    area = ""
                    //}, new { target = "_blank" })));
                    w.WriteLine(this.getViewPublishedLink());
                }
                if (_useDraft && this.CurrentlyRenderingNode.IsPublished)
                {
                    w.WriteLine(MvcHtmlString.Create(" | " + LinkExtensions.ActionLink(html, "Revert", "Revert", new { id = this.CurrentlyRenderingNode.PublishableNodeId })));
                }
                w.WriteLine(MvcHtmlString.Create("&nbsp;| " + LinkExtensions.ActionLink(html, "Trash", "Trash", new { id = this.CurrentlyRenderingNode.PublishableNodeId })));
                if (_childCount > 1)
                {
                    w.WriteLine(MvcHtmlString.Create(" | " + LinkExtensions.ActionLink(html, "Sort children", "SortChildren", new { id = this.CurrentlyRenderingNode.PublishableNodeId })));
                }
                if (!this.CurrentlyRenderingNode.IsRoot)
                {
                    w.WriteLine(MvcHtmlString.Create(" | " + LinkExtensions.ActionLink(html, "Move", "MoveSelector", new { id = this.CurrentlyRenderingNode.PublishableNodeId, selected = this.CurrentlyRenderingNode.PublishableNodeId })));
                }
                w.WriteLine(MvcHtmlString.Create("</span>"));
            }

            // check if open and has children - if so, render recursive
            if (_open && _hasChildren)
            {
                w.WriteLine(MvcHtmlString.Create("<ul>"));


                // iterate
                foreach (var child in _childNodes)
                {
                    // set rend
                    this.CurrentlyRenderingNode = child;

                    // render
                    this.RenderSiteMap(w, html);

                }

                w.WriteLine(MvcHtmlString.Create("</ul>"));
            }
            w.WriteLine(MvcHtmlString.Create("</li>"));
        }

        /// <summary>
        /// Get view published link
        /// </summary>
        /// <returns></returns>
        private MvcHtmlString getViewPublishedLink()
        {
            return _cachedUrl != null ? MvcHtmlString.Create(" | <a href=\"" + _liveLink + "\" target=\"_blank\">View published</a>") : MvcHtmlString.Create("");
        }

        // site map fields
        string _liveLink;
        CachedNodeUrl _cachedUrl;
        bool _liveTestOk;
        DraftNode _draft;
        bool _useDraft;
        string _name;
        bool _open;
        bool _selected;
        MvcHtmlString _linkQuery;
        string _linkClass;
        string _linkText;
        MvcHtmlString _link;
        IEnumerable<PublishableNode> _childNodes;
        PublishableNode _parent;
        IEnumerable<PublishableNode> _siblings;
        bool _hasParent;
        bool _hasChildren;
        bool _hasSiblings;
        int _childCount;
    }
}
